﻿using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;

namespace MetalSoda
{
    /// <summary>
    /// One to many or one association.
    /// </summary>
    /// <typeparam name="TRoot">Type of the association root</typeparam>
    /// <typeparam name="TRef">Type of the related object</typeparam>
    [Serializable]
    sealed class OneToManyAssociation<TRoot, TRef> : Collection<TRef>
        where TRoot : class
        where TRef : class
    {
        #region Nested Types

        /// <summary>
        /// Association changed event data
        /// </summary>
        public sealed class AssociationChangedEventArgs : EventArgs
        {
            /// <summary>
            /// Association root
            /// </summary>
            public readonly TRoot Root;

            /// <summary>
            /// Related object
            /// </summary>
            public readonly TRef Ref;

            [DebuggerStepThrough]
            internal AssociationChangedEventArgs(TRoot root, TRef @ref)
            {
                Root = root;
                Ref = @ref;
            }
        }

        #endregion

        /// <summary>
        /// Association root
        /// </summary>
        readonly TRoot Root;

        #region Private Fields
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly Action<AssociationChangedEventArgs> AssociationAdded;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        readonly Action<AssociationChangedEventArgs> AssociationRemoved; 
        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="MetalSoda.OneToManyAssociation{TRoot, TRef}"/> class.
        /// </summary>
        /// <param name="root">Association root</param>
        /// <param name="added">Item added handler</param>
        /// <param name="removed">Item removed handler</param>
        [DebuggerStepThrough()]
        public OneToManyAssociation(TRoot root, 
            Action<AssociationChangedEventArgs> added,
            Action<AssociationChangedEventArgs> removed)
        {
            if (Object.ReferenceEquals(root, null)) { throw new ArgumentNullException("root"); }
            if (added == null) { throw new ArgumentNullException("added"); }
            if (removed == null) { throw new ArgumentNullException("removed"); }
            Root = root;
            AssociationAdded = added;
            AssociationRemoved = removed;
        }

        #region Collection<TRef> overridden methods

        protected override void ClearItems()
        {
            var @refs = Items.ToList();
            base.ClearItems();
            @refs.ForEach(r => AssociationRemoved(new AssociationChangedEventArgs(Root, r)));
        }

        protected override void InsertItem(int index, TRef item)
        {
            if (Object.ReferenceEquals(item, null)) { return; }
            if (Contains(item)) { return; }
            base.InsertItem(index, item);
            AssociationAdded(new AssociationChangedEventArgs(Root, item));
        }

        protected override void RemoveItem(int index)
        {
            var item = Items[index];
            base.RemoveItem(index);
            AssociationRemoved(new AssociationChangedEventArgs(Root, item));
        }

        protected override void SetItem(int index, TRef item)
        {
            var item2 = Items[index];
            if (Contains(item))
            {
                var index2 = IndexOf(item);
                if (index2 == index) { return; }
                Items[index] = item;
                Items[index2] = item2;
                return;
            }
            base.SetItem(index, item);
            AssociationRemoved(new AssociationChangedEventArgs(Root, item2));
            AssociationAdded(new AssociationChangedEventArgs(Root, item));
        } 
        #endregion

    }
}
